package com.herui.sdyu_lib.http.okhttp;

import android.text.TextUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.herui.sdyu_lib.http.HttpUtils;
import com.herui.sdyu_lib.http.bean.KeyValue;
import com.herui.sdyu_lib.http.callback.Callback;
import com.herui.sdyu_lib.http.callback.Callback.*;
import com.herui.sdyu_lib.http.param.BaseParams;
import com.herui.sdyu_lib.http.param.RequestParams;
import com.herui.sdyu_lib.http.utils.Exceptions;
import com.herui.sdyu_lib.http.utils.HttpTool;
import com.herui.sdyu_lib.http.utils.Platform;
import com.herui.sdyu_lib.utils.LogUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import retrofit2.Retrofit;

/**
 * Created by amanda.shan on 2016/6/2.
 */
public class OKHttpRequestCall {

    private volatile static OkHttpClient okHttpClient;
    private volatile static Platform platform;
    private Retrofit retrofit;

    private RequestParams requestParams;

    /**
     * 默认信任所有的证书 TODO 最好加上证书认证，主流App都有自己的证书
     *
     * @return
     */
    private static SSLSocketFactory createSSLSocketFactory() {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
        }

        return sSLSocketFactory;
    }

    private static class TrustAllManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws
                CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)

                throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            X509Certificate[] x509Certificates = new X509Certificate[0];
            return x509Certificates;
        }
    }

    private static class TrustAllHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    public OKHttpRequestCall(RequestParams requestParams) {
        this.requestParams = requestParams;
        if (okHttpClient == null) {
            synchronized (OKHttpRequestCall.class) {
                if (okHttpClient == null) {

                    OkHttpClient.Builder mBuilder = mBuilder = new OkHttpClient.Builder();
                    mBuilder.sslSocketFactory(createSSLSocketFactory());
                    mBuilder.hostnameVerifier(new TrustAllHostnameVerifier());
                    okHttpClient = mBuilder.build();

                    platform = Platform.get();
                }
            }
        }
    }

    public void downLoad(Callback.ProgressDownCallback<Void> callback) {
        final Call call = getCall(null);
        new FileDownloadTask(call, callback, platform).start();
    }

    public InputStream downLoad() throws IOException {
        final Call call = getCall(null);
        Response response = call.execute();
        if (response.isSuccessful())
            return response.body().byteStream();
        else {
            LogUtils.e("响应错误:" + response.code());
            throw new IOException(response.body().string());
        }
    }

    public <T> void call(final Callback.CommonCallback<T> callback) {

        Call call = getCall(callback);
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.onError(e, null, null);
            }

            @Override
            public void onResponse(Call call, final Response response) {

                try {
                    final T t = (T) handleResponse(response, callback.type);
                    platform.execute(new Runnable() {
                        @Override
                        public void run() {
                            callback.onSuccess(t);
                        }
                    });
                } catch (final Exception e) {
                    platform.execute(new Runnable() {
                        @Override
                        public void run() {
                            callback.onError(e, null, null);
                        }
                    });
                }
            }
        });
    }

    public <T> T call(Class<T> resultType) throws Exception {

        Call call = getCall(null);
        Response response = call.execute();

        if (resultType == Response.class) {
            return (T) response;
        } else {
            return handleResponse(response, resultType);
        }

    }

    public <T> List<T> callList(Class<T> resultType) throws Exception {

        Call call = getCall(null);
        Response response = call.execute();
        try {
            if (response.isSuccessful()) {
                String result = null;
                result = response.body().string();

                List<T> list = JSON.parseArray(result, resultType);
                return list;
            } else {
                Exceptions.RequestException("request fail code: %d   message: %s", response.code
                        (), response.message());
            }

        } catch (IOException e) {
            throw e;
        }
        return null;
    }

    private <T> T handleResponse(Response response, Type type) throws Exception {
        try {
            if (response.isSuccessful()) {
                String result = null;
                result = response.body().string();

                if (type == Object.class)
                    return (T) result;
                else if (type == String.class)
                    return (T) result;
                else if (type == InputStream.class)
                    return (T) response.body().byteStream();
                else if (type == JSONObject.class) {
                    T jsonObject = null;
                    try {
                        jsonObject = (T) JSON.parseObject(result);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (jsonObject != null) {
                        return jsonObject;
                    }
                } else if (type == JSONArray.class) {
                    JSONArray jsonArray = null;
                    try {
                        jsonArray = JSON.parseArray(result);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    if (jsonArray != null) {
                        return (T) jsonArray;
                    }
                } else {
                    Object obj = null;
                    try {
                        obj = JSON.parseObject(result, type);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (obj != null) {
                        return (T) obj;
                    }
                }
            } else {
                Exceptions.RequestException("request fail code: %d   message: %s", response.code
                        (), response.message());
            }

        } catch (IOException e) {
            throw e;
        }
        return null;
    }

    private <T> Call getCall(final Callback.CommonCallback<T> callback) {
        long readTimeOut = requestParams.getReadTimeOut();
        long writeTimeOut = requestParams.getWriteTimeOut();
        long connTimeOut = requestParams.getConnTimeOut();

        OkHttpClient clone = okHttpClient;
        Call call = null;
        if (readTimeOut > 0 || writeTimeOut > 0 || connTimeOut > 0) {
            readTimeOut = readTimeOut > 0 ? readTimeOut : HttpUtils.DEFAULT_MILLISECONDS;
            writeTimeOut = writeTimeOut > 0 ? writeTimeOut : HttpUtils.DEFAULT_MILLISECONDS;
            connTimeOut = connTimeOut > 0 ? connTimeOut : HttpUtils.DEFAULT_MILLISECONDS;

            clone = okHttpClient.newBuilder().readTimeout(readTimeOut, TimeUnit.MILLISECONDS)
                    .writeTimeout(writeTimeOut, TimeUnit.MILLISECONDS)
                    .connectTimeout(connTimeOut, TimeUnit.MILLISECONDS).build();

        }
        call = clone.newCall(getRequest(callback));

        return call;
    }

    private Request getRequest(final Callback.CommonCallback callback) {

        Request.Builder builder = new Request.Builder();
        builder = new Request.Builder().url(HttpTool.appendUrlParams(requestParams.getUri(),
                requestParams.getQueryStringParams(), requestParams.getCharset()));
        addHead(builder);
        switch (requestParams.getMethod()) {
            case GET:
                break;
            case POST:
                RequestBody requestBody = null;
                if (!TextUtils.isEmpty(requestParams.getBodyContent())) {
                    requestBody = RequestBody.create(requestParams.isJson() ? MediaType.parse
                                    ("application/json") : null,
                            requestParams.getBodyContent());
                } else if (requestParams.isMultipart() || requestParams.getFileParams().size() >
                        0) {
                    MultipartBody.Builder mulBuilder = new MultipartBody.Builder().setType
                            (MultipartBody.FORM);
                    addMulParam(mulBuilder);
                    requestBody = mulBuilder.build();
                } else if (requestParams.getBodyParams().size() > 0) {
                    FormBody.Builder formBuilder = new FormBody.Builder();
                    addFormParam(formBuilder);
                    requestBody = formBuilder.build();
                } else {
                    requestBody = RequestBody.create(requestParams.isJson() ? MediaType.parse
                                    ("application/json") : null,
                            "");
                }

                if (callback instanceof Callback.ProgressUpCallback || callback instanceof
                        ProgressCallback) {
                    requestBody = ProgressHelper.addProgressRequestListener(requestBody, new
                            ProgressRequestListener() {
                                @Override
                                public void onRequestProgress(long bytesWritten, long contentLength,
                                                              boolean done) {
                                    if (callback instanceof ProgressUpCallback) {
                                        ProgressUpCallback progressUpCallback = (ProgressUpCallback)
                                                callback;
                                        progressUpCallback.onUpLoading(contentLength,
                                                bytesWritten, done);
                                    } else if (callback instanceof ProgressCallback) {
                                        ProgressCallback progressUpCallback = (ProgressCallback)
                                                callback;
                                        progressUpCallback.onUpLoading(contentLength,
                                                bytesWritten, done);
                                    }

                                }
                            });
                }
                builder.post(requestBody);
                break;
        }
        return builder.build();
    }

    private void addHead(Request.Builder builder) {

        for (BaseParams.Header header : requestParams.getHeaders())
            if (header.setHeader)
                builder.header(header.key, header.value.toString());
            else
                builder.addHeader(header.key, header.value.toString());
    }

    private void addFormParam(FormBody.Builder formBuilder) {
        for (KeyValue keyValue : requestParams.getBodyParams())
            formBuilder.add(keyValue.key, keyValue.value.toString());
    }

    private void addMulParam(MultipartBody.Builder builder) {
        for (KeyValue keyValue : requestParams.getFileParams()) {
            if (keyValue.value instanceof File) {
                File file = (File) keyValue.value;
                LogUtils.d(file.getName());
                builder.addFormDataPart(keyValue.key, file.getName(),
                        RequestBody.create(MediaType.parse(HttpTool.getContentType(file)), file));
            } else if (keyValue.value instanceof byte[]) {
                builder.addFormDataPart(keyValue.key, null,
                        RequestBody.create(MediaType.parse(HttpTool.getContentType(null)),
                                (byte[]) keyValue.value));
            } else if (keyValue.value instanceof String) {
                builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + keyValue
                                .key + "\""),
                        RequestBody.create(null, keyValue.value.toString()));
            }
        }
    }
}
